/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package it.mdc.hearts.war.room.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import it.mdc.hearts.ejb.database.Users;
import it.mdc.hearts.ejb.database.UsersFacadeLocal;
import it.mdc.hearts.ejb.gameLogic.Card;
import static it.mdc.hearts.ejb.gameLogic.GlobalConstants.*;
import it.mdc.hearts.ejb.gameLogic.HeartsBOTSessionBeanLocal;
import it.mdc.hearts.ejb.gameLogic.HeartsModel;
import it.mdc.hearts.ejb.gameLogic.HeartsSessionBeanLocal;
import org.primefaces.push.PushContext;
import org.primefaces.push.PushContextFactory;

/**
 *
 * @author Administrator
 */
public class GameTable implements Serializable, Comparable<GameTable>{
    
    
    final int WAIT = 0;
    final int RUNNING = 1;
    final int FINISHED = 2;
    final int TERMINATED = 3;
    final String statusString[] = {"Wait", "Running", "Finished", "Terminated"};
    
    HeartsBOTSessionBeanLocal heartsBOTSessionBean = lookupHeartsBOTSessionBeanLocal();
    HeartsSessionBeanLocal heartsSessionBean = lookupHeartsSessionBeanLocal();
    UsersFacadeLocal usersFacadeBean = lookupUsersFacadeLocal();
    
    private String name;
    private UUID tableKey;
    private Integer ownerID;
    private HeartsModel model;
    
    private int aiPlayer;
    private int numPlayer;
    private int tableStatus;
    private ArrayList<UserInfo> viewerInfo;
    
    private UserInfo[] players = new UserInfo[4];
   
    private final Semaphore mutex;
    private final Semaphore threadMutex;
    
    private final Date tableDateTime;
    
    public GameTable(int userID) 
    {
        mutex = new Semaphore(1, true);
        threadMutex = new Semaphore(1, true);
        model = null;
        numPlayer = 0;
        for(int i = 0; i < 4; i++)
        {
            players[i] = null;
        }
        ownerID = userID;
        viewerInfo = new ArrayList<UserInfo>();
        tableStatus = WAIT;
        tableDateTime = new Date();
    }
    
    private int getPlayerIndex(int userID){
        int nPlayerPosition = 0;
        try {
            mutex.acquire();
            for(int i = 0; i < 4; i++)
            {
                if(players[i] != null)
                {
                    if(players[i].getUserID() == userID)
                    {
                        nPlayerPosition = i;
                    }
                }
            }
            mutex.release();
        } catch (InterruptedException ex) {
            Logger.getLogger(GameTable.class.getName()).log(Level.SEVERE, null, ex);
        }
        return nPlayerPosition;
    }
    
    private void notifyTableCard(GameTable currentTable)
    {
        final PushContext pushContext = PushContextFactory.getDefault().getPushContext();
        final String CHANNEL = "/" + currentTable.getTableKey() + "/cards";
        
        pushContext.push(CHANNEL, "refreshCard");
    }
    
    private void notifyTableStatus(GameTable currentTable)
    {
        final PushContext pushContext = PushContextFactory.getDefault().getPushContext();
        final String CHANNEL = "/" + currentTable.getTableKey();
        
        pushContext.push(CHANNEL, tableStatus);
        notifyMainRoom();
    }
    
    private void notifyMainRoom()
    {
        final PushContext pushContext = PushContextFactory.getDefault().getPushContext();
        final String CHANNEL = "/refreshPage";
        
        pushContext.push(CHANNEL, "refresh");
    }
    
    public ArrayList<UserInfo> getViewerInfo() {
        return viewerInfo;
    }
    
    public boolean addViewer(UserInfo userInfo) {
        if(!viewerInfo.contains(userInfo))
        {
            viewerInfo.add(userInfo);
        }
        notifyTableStatus(this);
        return true;
    }
    
    public boolean removeViewer(UserInfo userInfo) {
        viewerInfo.remove(userInfo);
        notifyTableStatus(this);
        return true;
    }
    
    public boolean removePlayer(UserInfo userInfo)
    {
        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try 
                {
                    threadMutex.acquire();
                    Thread.sleep(500);
                    evaluateAndPlayTurn(false);
                    threadMutex.release();
                } catch (InterruptedException ex) {
                    Logger.getLogger(GameTable.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        
        boolean bRet = true;
        int userID = userInfo.getUserID();
        
        try 
        {
            mutex.acquire();
            if((tableStatus != TERMINATED) && (tableStatus != FINISHED))
            { 
                
                if(ownerID == userID)
                {
                    tableStatus = TERMINATED;
                    notifyTableStatus(this);
                    notifyTableCard(this);
                }
                else
                {
                    int nPlayerIndex = 0;

                    bRet = false;

                    for(int i = 0; i < 4; i++)
                    {
                        if(players[i] != null)
                        {
                            if(players[i].getUserID() == userID)
                            {
                                nPlayerIndex = i;
                                bRet = true;
                            }
                        }
                    }

                    if(tableStatus == RUNNING)
                    {
                        if(bRet)
                        {
                            if(players[nPlayerIndex] != null)
                            {
                                Users userFind = usersFacadeBean.find(players[nPlayerIndex].getUserID());
                                userFind.setPoint(userFind.getPoint()-1);
                                usersFacadeBean.edit(userFind);
                                players[nPlayerIndex].setScore(userFind.getPoint());
                                players[nPlayerIndex] = null;
                                notifyMainRoom();
                            }
                        }
                        
                        threadMutex.acquire();
                        int currentPlayerIndex = model.getCurrentPlayer();
                        if(currentPlayerIndex == nPlayerIndex)
                        {
                            // start the thread
                            thread.start(); 
                        }
                        threadMutex.release();
                    }
                    notifyTableCard(this);
                }
            }
            mutex.release();
        }
        catch (InterruptedException ex) {
            Logger.getLogger(GameTable.class.getName()).log(Level.SEVERE, null, ex);
        }
        return bRet;
    }
    
    public boolean setPlayer(UserInfo userInfo) 
    { 
        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try 
                {
                    threadMutex.acquire();
                    Thread.sleep(2000);
                    evaluateAndPlayTurn(false);
                    threadMutex.release();
                } 
                catch (InterruptedException ex) {
                    Logger.getLogger(GameTable.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        
        boolean bRet = false;
        try 
        {   
            mutex.acquire();
            
            for(int i= 0; (i < 4) && !bRet; i++)
            {
                if(players[i] != null)
                {
                    if(players[i].getUserID() == userInfo.getUserID())
                    {
                        players[i] = userInfo;
                        bRet = true;
                    }
                }
            }
            
            if(((numPlayer + aiPlayer) < 4) && !bRet)
            {
                int nPosition = (int)(Math.random() * 4);
                
                while(players[nPosition] != null)
                {
                    nPosition = (nPosition+1)%4;
                }
                
                players[nPosition] = userInfo;
//                userInfo.setStatusCode(userInfo.WAIT);
                
                numPlayer++;
                if((numPlayer + aiPlayer) >= 4)
                {
                    model = heartsSessionBean.initHeartsModel();
                    tableStatus = RUNNING;
                    
                    threadMutex.acquire();
                    int currentPlayerIndex = model.getCurrentPlayer();
                    if(players[currentPlayerIndex] == null)
                    {
                        // start the thread
                        thread.start(); 
                    }
                    threadMutex.release();
                }
                
                bRet = true;
            }
            mutex.release();
        } 
        catch (InterruptedException ex){
            bRet = false;
        }
        
        notifyTableStatus(this);
        notifyTableCard(this);
        
        return bRet;
    }
    
    public ArrayList<Card> getPlayerDeck(int userID)
    {
        if(model == null)
        {
            ArrayList<Card> tempArray = new ArrayList<Card>();
            return tempArray;
        }
        return model.getPlayerDeck(getPlayerIndex(userID));
    }
    
    public ArrayList<Card> getPlayedCard(int userID) {
        int userIndex = getPlayerIndex(userID);
        ArrayList<Card> playedCard = new ArrayList<Card>();
        Card nullCard = new Card();
        
        if(model != null)
        {
            for(int i = 0; i < 4; i++)
            {
                Card temp = model.getPlayedCard(userIndex);
                if(temp == null)
                {
                    playedCard.add(nullCard);
                }
                else
                {
                    playedCard.add(temp);
                }
                userIndex = (userIndex+1)%4;
            }
        }
        else
        {
            for(int i = 0; i < 4; i++)
            {
                playedCard.add(nullCard);
            }
        }
        return playedCard;
    }
    
    public int[] getPlayerCardNumber(int userID)
    {
        int userIndex = getPlayerIndex(userID);
        int[] cardNumber = {0,0,0,0};
        
        if(model != null)
        {
            for(int i = 0; i < 4; i++)
            {
                cardNumber[i] = model.getPlayerCardNumber(userIndex);
                userIndex = (userIndex+1)%4;
            }
        }

        return cardNumber;
    }
    
    public ArrayList<Card>[] getPlayersDeckTaken(Integer userID) 
    {
        ArrayList<Card>[] playersDeckTaken = (ArrayList<Card>[])new ArrayList[4];
        int userIndex = getPlayerIndex(userID);
        if(model != null)
        {
            for(int i = 0; i < 4; i++)
            {
                playersDeckTaken[i] = model.getPlayerDeckTaken(userIndex);
                userIndex = (userIndex+1)%4;
            }
        }
        return playersDeckTaken;
    }
    
    public int[] getPlayersPoint(Integer userID) 
    {
        int[] playersPoint = new int[4];
        int userIndex = getPlayerIndex(userID);
        if(model != null)
        {
            for(int i = 0; i < 4; i++)
            {
                playersPoint[i] = model.getPlayerPoint(userIndex);
                userIndex = (userIndex+1)%4;
            }
        }
        return playersPoint;
    }

    public ArrayList<UserInfo> getPlayerInfo(Integer userID) {
        ArrayList<UserInfo> playerInfo = new ArrayList<UserInfo>();
        int playerIndex = getPlayerIndex(userID);
        try {
            mutex.acquire();
            for(int i=0;i<4;i++){
                UserInfo tempInfo = players[playerIndex];
                if(tempInfo == null)
                {
                    tempInfo = new UserInfo();
                    tempInfo.setUsername("BOT" + playerIndex);
                }
                
                playerInfo.add(tempInfo);
                playerIndex = (playerIndex +1)%4;
            }
            mutex.release();   
        } catch (InterruptedException ex) {
            Logger.getLogger(GameTable.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return playerInfo;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }

    public int getAiPlayer() {
        return aiPlayer;
    }

    public void setAiPlayer(int aiPlayer) {
        this.aiPlayer = aiPlayer;
    }

    public int getNumPlayer() {
        return numPlayer;
    }

    public UUID getTableKey() {
        return tableKey;
    }

    public void setTableKey(UUID tableKey) {
        this.tableKey = tableKey;
    }  

    public Integer getOwnerID() {
        return ownerID;
    }

    public int getTableStatus() {
        return tableStatus;
    }
    
    public String getTableStatusString()
    {
        return statusString[tableStatus];
    }
    
    public boolean playCard(int userID, Card card)
    {
        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try 
                {
                    threadMutex.acquire();
                    Thread.sleep(500);
                    evaluateAndPlayTurn(true);
                    threadMutex.release();
                    
                } 
                catch (InterruptedException ex) {
                    Logger.getLogger(GameTable.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        
        boolean bRet = false;
        int playerIndex = getPlayerIndex(userID);
        if(heartsSessionBean.playCard(model, playerIndex, card))   
        {
            notifyTableCard(this);
            bRet = true;
            thread.start();
        }
        else
        { 
            notifyTableCard(this);
        }
        return bRet;
    }
    
    @SuppressWarnings("SleepWhileInLoop")
    private void evaluateAndPlayTurn(boolean evaluateStep)
    {
        boolean bContinue = true; 
        try 
        {   
            do
            {
                if(evaluateStep)
                {
                    int state = heartsSessionBean.evaluateStep(model);
                    if(state != END_GAME)
                    {
                        if(state == END_TURN)
                        {
                            Thread.sleep(1000);   
                        }
                        notifyTableCard(this);
                    }
                    else
                    {
                        mutex.acquire();
                        tableStatus = FINISHED;
                        int[] playerPoint = new int[4];
                        int minPoint = 26;
                        
                        for(int i = 0; i < 4; i++)
                        {
                            playerPoint[i] = model.getPlayerPoint(i);
                            minPoint = Math.min(playerPoint[i], minPoint);
                        }
                        
                        for(int i = 0; i < 4; i++)
                        {
                            if(players[i] != null)
                            {
                                Users userFind = usersFacadeBean.find(players[i].getUserID());
                                if(playerPoint[i] == minPoint)
                                {
                                    userFind.setPoint(userFind.getPoint()+4);
                                }
                                else
                                {
                                    userFind.setPoint(userFind.getPoint()-1);
                                }
                                usersFacadeBean.edit(userFind);
                                players[i].setScore(userFind.getPoint());
                            }
                        }
                        
                        mutex.release();
                        
                        bContinue = false;
                        
                        notifyTableCard(this);
                        notifyTableStatus(this);
                    }
                }
                
                int currentPlayerIndex = model.getCurrentPlayer();
                
                mutex.acquire();
                UserInfo user = players[currentPlayerIndex];
                mutex.release();
                
                if((user == null) && bContinue)
                {
                    heartsBOTSessionBean.playCard(model, currentPlayerIndex);
                    notifyTableCard(this);    
                    
                    Thread.sleep(500);
                    evaluateStep = true;
                }
                else
                {
                    bContinue = false;
                }
            }
            while(bContinue);

            notifyTableCard(this);
        } 
        catch (InterruptedException ex) {
            Logger.getLogger(GameTable.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public boolean isPlayer(Integer userID) {
        boolean bRet = false;
        try
        {
            mutex.acquire();
            for(int i= 0; i < 4; i++)
            {
                if(players[i] != null)
                {
                    if(players[i].getUserID() == userID)
                    {
                        bRet = true;
                    }
                }
            }
            mutex.release();
        }
        catch (InterruptedException ex) {
            Logger.getLogger(GameTable.class.getName()).log(Level.SEVERE, null, ex);
        }
        return bRet;
    }

     private HeartsSessionBeanLocal lookupHeartsSessionBeanLocal() {
        try {
            Context context = new InitialContext();
            return (HeartsSessionBeanLocal)context.lookup("java:global/HeartsSessionBean");
        } catch (NamingException ne) {
            Logger.getLogger(getClass().getName()).log(Level.SEVERE, "exception caught", ne);
            throw new RuntimeException(ne);
        }
    }
     
    private HeartsBOTSessionBeanLocal lookupHeartsBOTSessionBeanLocal() {
        try {
            Context context = new InitialContext();
            return (HeartsBOTSessionBeanLocal) context.lookup("java:global/HeartsBOTSessionBean");
        } catch (NamingException ne) {
            Logger.getLogger(getClass().getName()).log(Level.SEVERE, "exception caught", ne);
            throw new RuntimeException(ne);
        }
    }

    private UsersFacadeLocal lookupUsersFacadeLocal() {
        try {
            Context context = new InitialContext();
            return (UsersFacadeLocal) context.lookup("java:global/UsersFacade");
        } catch (NamingException ne) {
            Logger.getLogger(getClass().getName()).log(Level.SEVERE, "exception caught", ne);
            throw new RuntimeException(ne);
        }
    }
    
    @Override
    public int compareTo(GameTable o) {
        return -this.tableDateTime.compareTo(o.tableDateTime);
    }
}
